package com.spring.factory;


import com.mysql.jdbc.StringUtils;
import com.spring.annotation.Autowired;
import com.spring.annotation.Component;
import com.spring.proxy.CglibProxyStrategy;
import com.spring.proxy.JdkProxyStrategy;
import com.spring.proxy.ProxyStrategy;
import com.spring.util.ClassUtils;
import com.spring.util.ReflectionUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 *  @author: zheng
 *  @Date: 2020-06-05 20:43
 *  @Description:初始化Bean,依赖注入
 */
public class DefaultBeanFactory implements BeanFactory{

    private static volatile  DefaultBeanFactory factory;
    //一级缓存
    private Map<String, Object> finalPool = new ConcurrentHashMap<String, Object>();
    //二级缓存
    private Map<String, Object> earlyPool = new ConcurrentHashMap<String, Object>();
    //三级缓存
    private Map<String, Object> initialPool = new ConcurrentHashMap<String, Object>();
    //代理模式
    private Map<String, ProxyStrategy> strategyMap = new HashMap<String, ProxyStrategy>();
    //class 和id映射关系
    private Map<Class<?>, String> idMapping = new HashMap<Class<?>, String>();

     //实力化代理模式
    public DefaultBeanFactory(){
        this.strategyMap.put("jdk", new JdkProxyStrategy());
        this.strategyMap.put("cglib", new CglibProxyStrategy());
    }
    //双重校验锁单利模式
    public  static DefaultBeanFactory getInstance(){
        if(factory==null){
            synchronized (DefaultBeanFactory.class){
                if(factory==null){
                    factory= new DefaultBeanFactory();
                }
            }
        }
        return factory;
    }

    //IOC 容器初始化
    public void init() throws Exception {

        //获取扫描路径
        String packageName = getPackageName();
        //获取扫描路径下的所有class
        Set<Class> classes = doScan(packageName);
        //查找对应注解class和设置bean
        findAnnotationAndSetBean(classes);


    }




    public String getPackageName() throws DocumentException {
        InputStream inputStream = DefaultBeanFactory.class.getClassLoader().getResourceAsStream("applicationContext.xml");
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element rootElement = document.getRootElement();
        Element scanElement = (Element) rootElement.selectSingleNode("//component-scan");
        String packageName = scanElement.attributeValue("base-package");
        return packageName;

    }

    public Set doScan(String packageName) throws Exception {

        return ClassUtils.getAllClasses(packageName);

    }

    public void findAnnotationAndSetBean(Set<Class> classes) throws Exception {

        for (Class c : classes) {

            if(findAnnotation(c)!=null)
            setBean(c);

        }

    }
    public Class findAnnotation(Class clazz) throws Exception {
        //如果class是注解则返回空
        if (clazz.isAnnotation()) {
            return null;
        }
        //返回被注解标记的class
        return ReflectionUtils.getAnnotation(clazz, Component.class);
    }

    public void setBean(Class clazz) throws Exception {

        String id = getIdByClass(clazz);
        String proxyMode = null;
        if(!clazz.isInterface() && clazz.getInterfaces().length == 0)
            proxyMode = "cglib";
        else
            proxyMode ="jdk";

        if(id != null) {
            // 先检查一级缓存
            if(finalPool.get(id) != null)
                return;

            // 再检查二级缓存
            Object bean = earlyPool.get(id);
            if(bean != null){
                setFinalPool(id, bean);
                return;
            }

            // 再检查三级缓存
            bean = initialPool.get(id);
            if(bean != null){
                setEarlyPool(id, bean, proxyMode);
                Object proxyBean = earlyPool.get(id);
                setFinalPool(id, proxyBean);
                return;
            }
        }

        //获取注解是否有默认的值
        String value = ReflectionUtils.getBeanValue(clazz);
        //如果没有默认值那么就使用类名，否则使用添加的默认值
        if(StringUtils.isNullOrEmpty(value))
            id = clazz.getName();
        else
            id = value;
        //存入class 对应的id表
        idMapping.put(clazz, id);

        //第一步 初始化bean并放入三级缓存
        Object bean = clazz.newInstance();
        initialPool.put(id,bean);

        //第二步 填充bean的属性
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field f : declaredFields) {
            if(f.isAnnotationPresent(Autowired.class)){
                Class<?> fieldClazz = f.getType();
                //取出bean
                Object fieldBean = getBean(fieldClazz);
                if(fieldBean == null){
                    //递归调用
                    setBean(fieldClazz);
                    fieldBean = getBean(fieldClazz);
                }
                ReflectionUtils.setObject(f, bean, fieldBean);
            }
        }

        //设置一二级别缓存
        if(finalPool.get(id) == null){
            if(earlyPool.get(id) == null) {
                setEarlyPool(id, bean, proxyMode);
            }
            Object proxyBean = earlyPool.get(id);
            setFinalPool(id, proxyBean);
        }
    }
    //根据class查找id映射文件中是否有对应的实现类，如果class是接口，则会找出接口中所有的实现类
    //如果接口找出的实现类是多个或者无,则提示错误
    private String getIdByClass(Class<?> clazz) throws Exception {

        Class<?> implementation = null;
        List<Class> classes = ClassUtils.getAllClassesByInterface(clazz);
        int classesLength = classes.size();
        switch (classesLength) {
            case 0:
                throw new RuntimeException("Can not find any implementation.");
            case 1:
                implementation = classes.get(0);
                break;
            default:
                throw new RuntimeException("Can not find unique implementation.");
        }

        return idMapping.get(implementation);
    }
    //设置代理对象
    private void setEarlyPool(String name, Object bean, String proxyMode) {
        ProxyStrategy proxyStrategy = strategyMap.get(proxyMode);
        Object proxyBean = proxyStrategy.doProxy(bean);
        this.earlyPool.put(name, proxyBean);
        this.initialPool.remove(name);
    }

    private void setFinalPool(String name, Object bean) {
        this.finalPool.put(name, bean);
        this.earlyPool.remove(name);
    }



    @Override
    public <T> T getBean(String name) {
        return (T)finalPool.get(name);
    }

    @Override
    public <T> T getBean(Class cls) throws Exception {
        String id = getIdByClass(cls);
        if (StringUtils.isNullOrEmpty(id))
            return null;
        else
            return (T)getBean(id);
    }
}
